package com.iteaj.iot.client.component;

import cn.hutool.core.collection.CollectionUtil;
import com.iteaj.iot.*;
import com.iteaj.iot.client.*;
import com.iteaj.iot.client.protocol.ClientSocketProtocol;
import com.iteaj.iot.client.protocol.ServerInitiativeProtocol;
import com.iteaj.iot.codec.SocketMessageDecoder;
import com.iteaj.iot.codec.filter.DecoderInterceptor;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.util.ReferenceCounted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.util.List;

/**
 * 基于socket的客户端组件 主要tcp, upd等
 * @param <M>
 */
public abstract class SocketClientComponent<M extends ClientMessage, R extends ReferenceCounted> extends SocketProtocolFactory<M>
        implements ClientComponent<M>, SocketMessageDecoder<R>, IotProtocolFactory<M> {

    /**
     * 是否启用
     */
    private boolean start;

    /**
     * 开启时间
     */
    private long startTime;

    /**
     * 解码拦截器
     */
    private DecoderInterceptor interceptor;

    /**
     * 默认客户端
     * @see #config
     */
    private SocketClient iotClient;

    /**
     * 报文类型
     */
    private Class<M> messageClass;

    /**
     * 报文类型构造函数
     */
    private Constructor<M> constructor;

    /**
     * 默认客户端配置
     * @see #iotClient
     */
    private ClientConnectProperties config;

    /**
     * 多客户端管理器
     */
    private MultiClientManager clientManager;
    protected Logger logger = LoggerFactory.getLogger(getClass());

    public SocketClientComponent() {
        this(null);
    }

    /**
     * @param config 默认客户端
     */
    public SocketClientComponent(ClientConnectProperties config) {
        this(config, new SimpleMultiClientManager());
    }

    public SocketClientComponent(ClientConnectProperties config, MultiClientManager clientManager) {
        this.config = config;
        this.clientManager = clientManager;
        this.setDelegation(createProtocolTimeoutStorage());
    }

    @Override
    public SocketMessage proxy(ChannelHandlerContext ctx, R in) throws Exception {
        ClientMessage socketMessage = (ClientMessage) SocketMessageDecoder.super.proxy(ctx, in);
        // 设置此客户端的配置信息
        if(socketMessage != null) {
            socketMessage.setProperties(ctx.channel().attr(CoreConst.CLIENT_KEY).get());
        }

        return socketMessage;
    }

    @Override
    protected abstract ServerInitiativeProtocol<M> doGetProtocol(M message, ProtocolType type);

    @Override
    public void start(Object config) {
        if(!this.isStart()) {
            this.start = true;
            this.startTime = System.currentTimeMillis();
            if(this.interceptor == null) {
                this.interceptor = DecoderInterceptor.DEFAULT;
            }

            if(getConfig() != null) {
                synchronized (this) {
                    this.getClient().init(config);
                    this.getClient().connect();
                }
            }
        }
    }

    @Override
    public void close() {
        if(this.start) {
            this.start = false;
            List<IotClient> clients = this.clientManager.clients();
            if(CollectionUtil.isNotEmpty(clients)) {
                clients.forEach(item -> item.close());
            }
        }
    }

    @Override
    public void finished() {
        ClientComponent.super.finished();
    }

    @Override
    public void addClient(Object clientKey, IotClient value) {
        this.clientManager.addClient(clientKey, value);
    }

    @Override
    public SocketClient getClient() {
        if(this.iotClient != null) {
            return this.iotClient;
        } else {
            if(this.getConfig() != null) {
                return this.iotClient = createNewClient(this.config);
            }
        }

        return null;
    }

    /**
     * 写出报文
     * @param properties 客户端配置
     * @param msg 发送的协议
     * @param args 自定义参数
     * @return
     */
    public ChannelFuture writeAndFlush(ClientConnectProperties properties, Object msg, Object... args) {
        SocketClient socketClient = this.getOrElseCreate(properties);
        return socketClient.writeAndFlush(msg, args);
    }

    /**
     * 写出协议
     * @see Protocol#requestMessage() 请求的报文
     * @see Protocol#responseMessage() 响应的报文
     * @param properties 客户端配置
     * @param protocol 要写出的协议
     * @return
     */
    public ChannelFuture writeAndFlush(ClientConnectProperties properties, ClientSocketProtocol protocol) {
        return this.writeAndFlush(properties, protocol, null);
    }

    @Override
    public List<IotClient> clients() {
        return clientManager.clients();
    }

    @Override
    public SocketClient getClient(Object clientKey) {
        return (SocketClient) clientManager.getClient(clientKey);
    }


    /**
     * 获取客户端 如果不存在则创建
     * @param properties
     * @return
     */
    public synchronized SocketClient getOrElseCreate(ClientConnectProperties properties) {
        SocketClient client = this.getClient(properties);
        if(client == null) {
            return this.createNewClientAndConnect(properties);
        }

        return client;
    }

    @Override
    public IotClient removeClient(Object clientKey) {
        return clientManager.removeClient(clientKey);
    }

    @Override
    public Class<M> getMessageClass() {
        if(this.messageClass == null) {
            // @since 2.3.0 只解析一次报文类
            this.messageClass = ClientComponent.super.getMessageClass();

            // 注意：只在空的时候解析一次
            this.resolveConstructor();
        }

        return this.messageClass;
    }

    @Override
    public SocketMessage createMessage(byte[] message) {
        final Constructor<M> constructor = this.resolveConstructor();
        try {
            return constructor.newInstance(message);
        } catch (ReflectiveOperationException e) {
            throw new FrameworkException(e);
        }
    }

    private Constructor<M> resolveConstructor() {
        if(this.constructor == null) {
            try {
                this.constructor = this.getMessageClass().getConstructor(byte[].class);
            } catch (NoSuchMethodException e) {
                final String simpleName = this.getMessageClass().getSimpleName();
                throw new ProtocolException("报文类型缺少构造函数["+simpleName+"(byte[])]", e);
            }
        }

        return this.constructor;
    }

    /**
     * 创建一个新客户端
     * @param config
     * @return
     */
    @Override
    public abstract SocketClient createNewClient(ClientConnectProperties config);

    /**
     * 创建新的客户端并连接
     * @param config
     */
    public SocketClient createNewClientAndConnect(ClientConnectProperties config) {
        SocketClient newClient = createNewClient(config);
        newClient.init(null);

        // 创建连接
        ChannelFuture connect = newClient.connect();
        boolean await = connect.awaitUninterruptibly(config.getConnectTimeout());
        if(!await) {
            throw new FrameworkException("连接超时["+config.getConnectTimeout()+"]");
        }

        if(!connect.isSuccess()) {
            throw new ProtocolException("连接异常", connect.cause());
        }

        return newClient;
    }

    @Override
    public Object idle(String deviceSn, IdleState state) {
        return getInterceptor().idle(deviceSn, state);
    }

    @Override
    public boolean isActivation(Channel channel, FrameworkComponent component) {
        return getInterceptor().isActivation(channel, component);
    }

    @Override
    public boolean isDecoder(Channel channel, ReferenceCounted msg) {
        return getInterceptor().isDecoder(channel, msg);
    }

    @Override
    public long startTime() {
        return startTime;
    }

    @Override
    public DecoderInterceptor getInterceptor() {
        return this.interceptor;
    }

    public void setInterceptor(DecoderInterceptor interceptor) {
        this.interceptor = interceptor;
    }

    @Override
    public IotProtocolFactory protocolFactory() {
        return this;
    }

    public MultiClientManager getClientManager() {
        return clientManager;
    }

    protected ProtocolTimeoutStorage createProtocolTimeoutStorage() {
        return new ProtocolTimeoutStorage(getName());
    }

    @Override
    public ClientConnectProperties getConfig() {
        return config;
    }

    public void setConfig(ClientConnectProperties config) {
        this.config = config;
    }

    @Override
    public boolean isStart() {
        return start;
    }
}
